package com.iflytek.sbd.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;


/**
 * @program:
 * @description: 对于spring提供的@Cacheable没有缓存失效时间,基于目录定义缓存失效时间
 * @author: cl
 * @create: 2021-09-23 16:56
 */
@Configuration
@EnableCaching // 添加依赖,启动类开始缓存,才可以对方法的结果进行缓存,效果是: 多次调用可以查询缓存
@ConditionalOnExpression("'${spring.cache.type:}'.equals('redis')") // 添加这个注解需要配置文件配置 spring.cache.type 不配置的话,该类不生效
public class RedisSetKeyExpireTimeConfig {
    /**
     * 方法缓存自定义缓存时间,单位秒
     */
    @Value("${sbd.expireTime}")
    private long expireTime;

    /**
     * 自定义redis key生成规则，防止json串中:导致redis目录分层，不易于查询
     */
    @Bean
    public KeyGenerator customRedisKeyGenerator() {
        return (target, method, params) -> {
            String param = String.valueOf(params[0]);
            return method.getName() + ":" +
                    (StringUtils.isBlank(param) ? params : param.replace(":", "_").trim());
        };
    }

    @Bean
    public CacheManager customCacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(factory))
                .withInitialCacheConfigurations(getRedisCacheConfigurationMap())
                .transactionAware()
                .build();
    }

    /**
     * 基于cacheName配置不同的redis config
     * 基于目录配置缓存失效时间
     * 此处目录可修改
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(1);
        /**
         * @Cacheable(value = "computer::get1",key = "#p0.id")
         * @Cache缓存key: computer::get1::004,此时下面put-> computer::get1, 存储的004有过期时间
         * @Cache缓存key: computer::get1::004,此时下面put-> computer ,存储的004没有过期时间
         *
         * @Cacheable(value = "computer",key = "#p0.id")
         * @Cache缓存key: computer::001,此时下面put-> computer, 存储的001有过期时间
         *
         * @Cacheable(value = "computer:test:get1",key = "#p0.id")
         * @Cache缓存key: computer:test:get1,此时下面put-> computer, 存储的001没有过期时间
         * @Cache缓存key: computer:test:get1,此时下面put-> computer:test, 存储的001没有过期时间
         * @Cache缓存key: computer:test:get1,此时下面put-> computer:test:get1, 存储的001有过期时间
         *
         * 结论:下次进行方法结果缓存,需要设置缓存失效时间, 下面的put的文件夹名需要与@Cacheable的value属性配置值一致
         */
        redisCacheConfigurationMap.put("computer:test:get1", this.getRedisCacheConfigurationWithTtl(expireTime));
        System.out.println("redis的键过期时间设置成功" + expireTime);
        return redisCacheConfigurationMap;
    }

    /**
     * 配置redis config的ttl时间、序列化策略
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(long seconds) {
        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()))
                .entryTtl(Duration.ofSeconds(seconds));
    }
}
